package org.fhawk.tinyspring.orm;

import org.fhawk.tinyspring.core.ClassHelper;
import org.fhawk.tinyspring.core.ConfigHelper;
import org.fhawk.tinyspring.orm.annotation.AutoIncrement;
import org.fhawk.tinyspring.orm.annotation.Entity;
import org.fhawk.tinyspring.orm.annotation.ID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by tuo on 10/8/16.
 * 主要用户更新表
 */
public class DataBaseHelper {
    private static Connection connection;
    private static Logger logger = LoggerFactory.getLogger(DataBaseHelper.class);

    private DataBaseHelper() {
    }

    static {
        try {
            connection = ConnectionFactory.getConnection();
        } catch (SQLException e) {
            logger.error("获取数据库链接失败。",e);
        }
        String url = ConfigHelper.getString("org.fhawk.jdbc.url");
        //只针对mysql 进行自动创建表和更新字段
        if (url.startsWith("jdbc:mysql:")) {
            List<Class<?>> classList = ClassHelper.getBasePackageClassListByAnnotation(Entity.class);
            Map<String, List<String>> tables = getTablesInfo();
            for (Class<?> clazz : classList) {
                String tableName = clazz.getSimpleName().toLowerCase();
                if (tables.containsKey(tableName)) {//表是否已经存在，存在则检查表的字段是否由修改
                    List<String> fieldNames = Arrays.stream(clazz.getDeclaredFields()).map(Field::getName).collect(Collectors.toList());
                    List<String> columnNames = tables.get(tableName);
                    //属性相对于列的差集表示需要添加的属性
                    List<String> addField = new ArrayList<>(fieldNames);
                    addField.removeAll(columnNames);
                    updateColumn(clazz, addField, true);
                    //列对于属性的差集表示需要删除的列
                    List<String> deleteColumn = new ArrayList<>(columnNames);
                    deleteColumn.removeAll(fieldNames);
                    updateColumn(clazz, deleteColumn, false);
                } else {//需要创建表
                    creatTable(clazz);
                }
            }
        }
    }


    private static Map<String, List<String>> getTablesInfo() {
        Map<String, List<String>> tableInfos = new HashMap<>();
        try {
            DatabaseMetaData data = connection.getMetaData();
            ResultSet set = data.getTables(null, null, null, new String[]{"TABLE"});
            while (set.next()) {
                String tableName = set.getString(3);
                ResultSet colRet = data.getColumns(null, "%", set.getString(3), "%");
                List<String> columnNames = new ArrayList<>();
                while (colRet.next()) {
                    String columnName = colRet.getString("COLUMN_NAME");
                    columnNames.add(columnName);
                }
                tableInfos.put(tableName, columnNames);
            }
        } catch (SQLException e) {
            logger.error("获取现有表数据失败。",e);
        }
        return tableInfos;
    }

    /**
     * 创建表
     */
    private static void creatTable(Class<?> clazz) {
        StringBuilder sql = new StringBuilder();
        sql.append("CREATE TABLE ").append(clazz.getSimpleName().toLowerCase()).append("(");
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            sql.append(" ").append(field.getName());
            sql.append(" ").append(checkSqlType(field.getType().getTypeName()));
            if (field.isAnnotationPresent(ID.class)) {
                sql.append(" PRIMARY KEY");
                if (field.isAnnotationPresent(AutoIncrement.class)) {
                    sql.append(" AUTO_INCREMENT");
                }
            }
            sql.append(",");
        }
        //删掉最后一个多余的逗号
        sql.deleteCharAt(sql.length() - 1);
        //添加上结尾的后括号
        sql.append(")");
        try (PreparedStatement statement = connection.prepareStatement(sql.toString())) {
            statement.execute();
        } catch (SQLException e) {
            logger.error("创建表失败。",e);
        }
    }

    /**
     * 更新字段(添加或者删除)
     *
     * @param clazz      类
     * @param columnName 列名
     * @param add        true 添加/ false 删除
     */
    private static void updateColumn(Class<?> clazz, List<String> columnName, boolean add) {

        for (String fieldName : columnName) {
            try {
                Field field = clazz.getDeclaredField(fieldName);
                String sql;
                if (add) {
                    sql = "alter table " + clazz.getSimpleName() + " add " + fieldName + " " + checkSqlType(field.getType().getTypeName());
                } else {
                    sql = "ALTER TABLE " + clazz.getSimpleName() + " DROP " + fieldName;
                }
                try (PreparedStatement statement = connection.prepareStatement(sql)) {
                    statement.execute();
                }
            } catch (NoSuchFieldException | SecurityException | SQLException e) {
                logger.error("修改表{}字段{}失败", clazz.getSimpleName(), fieldName, e);
            }
        }
    }


    private static String checkSqlType(String type) {
        String sqlType;
        switch (type) {
            case "java.lang.String":
                sqlType = "varchar(255)";
                break;
            case "java.lang.byte[]":
                sqlType = "BLOB";
                break;
            case "int":
            case "java.lang.Integer":
                sqlType = "TINYINT";
                break;
            case "long":
            case "java.lang.Long":
                sqlType = "BIGINT";
                break;
            case "float":
            case "java.lang.Float":
                sqlType = "FLOAT";
                break;
            case "double":
            case "java.lang.Double":
                sqlType = "DOUBLE";
                break;
            case "char":
            case "java.lang.Character":
                sqlType = "CHAR";
                break;
            case "boolean":
            case "java.lang.Boolean":
                sqlType = "BIT";
                break;
            case "java.math.BigInteger":
                sqlType = "BIGINT";
                break;
            case "java.math.BigDecimal":
                sqlType = "DECIMAL(8,2)";
                break;
            default:
                sqlType = "varchar(255)";
                break;
        }
        return sqlType;
    }

}
